using namespace std;

#include<vector>
#include<fstream>
#include<sstream>
#include<iostream>
#ifdef USE_GPU
#include<CL/cl.hpp>
#include"clsafe.h"
#endif
#include"cl_constants.h"
#include"cl_interface.h"

cOpenCLInterface::cOpenCLInterface(){}

cOpenCLInterface::cOpenCLInterface(int platform_id, int device_id, const char * kernel_source_dir, vector<string> kernel_filenames,long seed){
#if USE_GPU
  samples_per_rng = MT_SAMPLES/MT_RNG_COUNT;
  cerr<<"Initializing GPU with platform id "<<platform_id<<" and device id "<<device_id<<".\n";
  vector<cl::Platform> platforms;
  err = cl::Platform::get(&platforms);
  cerr<<"Platform ID "<<platform_id<<" has name "<<platforms[platform_id].getInfo<CL_PLATFORM_NAME>().c_str()<<endl;
  cl_context_properties cps[3] = {CL_CONTEXT_PLATFORM,(cl_context_properties)(platforms[platform_id])(),0};
  context = new cl::Context(CL_DEVICE_TYPE_GPU,cps);
  devices = context->getInfo<CL_CONTEXT_DEVICES>();
  cerr<<"There are "<<devices.size()<<" devices\n";
  cerr<<"Device ID "<<device_id<<" is of type: ";
  cl_device_type dtype = devices[device_id].getInfo<CL_DEVICE_TYPE>();
  switch(dtype){
    case CL_DEVICE_TYPE_GPU:
      cerr<<"GPU\n";
      break;
    case CL_DEVICE_TYPE_CPU:
      cerr<<"CPU\n";
      break;
  }
  commandQueue = new cl::CommandQueue(*context,devices[device_id],CL_QUEUE_PROFILING_ENABLE,&err);
  clSafe(err,"Creating command queue");
  ostringstream full_source_str;
  vector<string>::iterator it;
  for(it=kernel_filenames.begin();it!=kernel_filenames.end();it++){
    ostringstream oss;
    oss<<kernel_source_dir<<*it;
    cerr<<"Opening "<<oss.str()<<endl;
    ifstream ifs(oss.str().data());
    clSafe(ifs.is_open()?CL_SUCCESS:-1,"kernel_path not found");
    string source_str(istreambuf_iterator<char>(ifs),(istreambuf_iterator<char>()));
    full_source_str<<source_str;
  }
  string source_str = full_source_str.str();
  // create a program object from kernel source
  cl::Program::Sources source(1,make_pair(source_str.c_str(),source_str.length()+1));
  program = new cl::Program(*context,source);
  err = program->build(devices);
  if(err!=CL_SUCCESS){
    cerr<<"Build failed:\n";
    string buffer;
    program->getBuildInfo(devices[0],CL_PROGRAM_BUILD_LOG,&buffer);
    cerr<<buffer<<endl;
  }else{
    cerr<<"Kernel sources successfully compiled!\n";
  }
  // Initialize the Mersenne Twister PRNG
  ostringstream oss_dat;
  oss_dat<<kernel_source_dir<<"MersenneTwister.dat";
  mt_struct_stripped *mt_parameters = new mt_struct_stripped[MT_RNG_COUNT];
  //mt_struct_stripped *mt_parameters = (mt_struct_stripped*) malloc(sizeof(mt_struct_stripped) * MT_RNG_COUNT);
  init_mersenne(oss_dat.str().data(),mt_parameters,seed);
  // Set up the OpenCL data structures
  kernel_mersenne = new cl::Kernel(*program,"mersenneTwister_kernel_cl");
  clSafe(err,"creating kernel_mersenne");
  buffer_mt_parameters = new cl::Buffer(*context,CL_MEM_READ_ONLY,sizeof(mt_struct_stripped)*MT_RNG_COUNT);
  clSafe(err,"creating buffer_mt_parameters");
  buffer_random_unif = new cl::Buffer(*context,CL_MEM_READ_WRITE,sizeof(float)*MT_SAMPLES);
  clSafe(err,"creating buffer_random_unif");
  // Set up the kernel arguments
  int arg = 0;
  err = kernel_mersenne->setArg(arg++,*buffer_random_unif);
  clSafe(err,"set kernel arg buffer_random_unif");
  err = kernel_mersenne->setArg(arg++,*buffer_mt_parameters);
  clSafe(err,"set kernel arg buffer_random_unif");
  err = kernel_mersenne->setArg(arg++,samples_per_rng);
  clSafe(err,"set kernel arg samples_per_rng");
  // transfer memory from host to GPU global memory
  err = commandQueue->enqueueWriteBuffer(*buffer_mt_parameters, CL_TRUE, 0,sizeof(mt_struct_stripped)*MT_RNG_COUNT, mt_parameters, NULL, NULL );
  clSafe(err, "write mt_parameters");
  cerr<<"OpenCL interface initialized successfully!\n";
#endif
}

void cOpenCLInterface::test_rng(){
#ifdef USE_GPU
  cl::Event kernel_exec_event;
  cerr<<"Will generate "<<MT_SAMPLES<<" random uniform variates\n";
  err = commandQueue->enqueueNDRangeKernel(*kernel_mersenne,cl::NullRange,cl::NDRange(MT_RNG_COUNT),cl::NDRange(BLOCK_WIDTH),NULL,&kernel_exec_event);
  clSafe(err,"launch kernel_mersenne");
  // fetch the data
  float * samples = new float[MT_SAMPLES];
  err = commandQueue->enqueueReadBuffer(*buffer_random_unif, CL_TRUE, 0, sizeof(float)*MT_SAMPLES,samples);
  clSafe(err,"read random samples");
  for(int i=(MT_SAMPLES-1);i>=0;--i){
    //cerr<<i<<":"<<samples[i]<<endl;
  }
  delete[]samples;
  // profile kernel execution
  long start = 1,end = 1;
  err = kernel_exec_event.getProfilingInfo(CL_PROFILING_COMMAND_START,&start);
  clSafe(err,"getting start of event");
  err = kernel_exec_event.getProfilingInfo(CL_PROFILING_COMMAND_END,&end);
  clSafe(err,"getting end of event");
  cerr<<"Start and end time is "<<start<<" and "<<end<<endl;
  cerr<<"Time in seconds: "<<(end-start)*1.e-9<<endl;
  cerr<<"OpenCL PRNG test completed\n";
#else
  cerr<<"GPU test_rng not implemented\n";
#endif
} 

// adapted from http://www.pgroup.com/lit/articles/insider/v2n2a4.htm 

void cOpenCLInterface::init_mersenne(const char * initial_state_file,mt_struct_stripped *mt_parameters, long seed){
  //srandom((unsigned int) (time(NULL)));
  //double num = (random() / (float) RAND_MAX) * 4194304;
  //unsigned int seed = (unsigned int) num;
  cerr<<"Using seed "<<seed<<endl;
  ifstream ifs(initial_state_file,ios::in|ios::binary);
  if (!ifs.is_open()){
    cerr<<"Attempting to open "<< initial_state_file<<endl;
    throw "Exception: file not found";
  }
  //cerr<<"DEBUG2:\n";
  for (unsigned int i = 0; i < MT_RNG_COUNT; i++) {
    char * cvec = reinterpret_cast<char *>(mt_parameters+i);
    ifs.read(cvec,sizeof(mt_struct_stripped)); 
    mt_parameters[i].seed = seed;
    //cerr<<i<<","<<mt_parameters[i].matrix_a<<","<<mt_parameters[i].mask_b<<","<<mt_parameters[i].mask_c<<","<<mt_parameters[i].seed<<endl;
  }
  ifs.close();
}
